﻿using System;
using System.Collections.Generic;
using Brain.Rendering;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Brain.Editor;
using Brain.Logic.AI;
using System.IO;
using Brain.IO;

namespace Brain
{
    public class Engine : IDisposable
    {
        /// <summary>
        /// The Running Engine Instance
        /// </summary>
        public static Engine Instance;

        /// <summary>
        /// If true, the Engine will record timing of everything
        /// </summary>
        public bool Debug { get; set; }

        /// <summary>
        /// Disposes the Engine, and all it's managers
        /// </summary>
        public void Dispose()
        {
            if (ScreenshotTarget != null && !ScreenshotTarget.IsDisposed)
            {
                ScreenshotTarget.Dispose();
                ScreenshotTarget = null;
            }

            for (int i = 0; i < AllManagers.Count; i++)
            {
                IEngineManager manager = AllManagers[i];
                if (manager is IDisposable)
                {
                    IDisposable disp = manager as IDisposable;
                    disp.Dispose();
                }
            }
            this.AllManagers = null;
            this.UpdateableManagers = null;
            this.DrawableManagers = null;

            this.AIManager = null;
            this.AnimationManager = null;
            this.CameraManager = null;
            this.ConsoleManager = null;
            this.ContentManager = null;
            this.DebugManager = null;
            this.EditorManager = null;
            this.Game = null;
            this.GameTime = null;
            this.GraphicsDevice = null;
            this.InputManager = null;
            this.Physics2DManager = null;
            this.Physics3DManager = null;
            this.RenderManager2D = null;
            this.RenderManager3D = null;
            this.ResourceManager = null;
            this.ScreenManager = null;
            this.SerializationManager = null;
            this.ServiceProvider = null;
            this.StorageDeviceManager = null;
            this.StereoscopicManager = null;
            this.TimeManager = null;
        }

        /// <summary>
        /// Erases everything the Engine has loaded
        /// </summary>
        public static void EndAndDispose()
        {
            if (Instance != null)
            {
                Instance.Dispose();
            }
            Instance = null;

            GC.Collect();
        }

        /// <summary>
        /// Resets the Engine's Instance
        /// </summary>
        public static void Restart()
        {
            if (Instance != null)
            {
                Instance.Reset();
            }
        }

        /// <summary>
        /// Initializes the Engine with all the Default Managers(RenderManagers and DevManagers
        /// are NOT added)
        /// </summary>
        /// <param name="graphicsService"></param>
        public static void InitializeEngineWithDefaultManagers(EngineFolders folders, IGraphicsDeviceService graphicsService)
        {
            Instance = new Engine();
            Instance.EngineFolders = folders;
            Instance.GraphicsDevice = graphicsService.GraphicsDevice;

            Instance.EngineFolders = folders;

            Instance.ScreenManager = new DefaultScreenManager();
#if WINDOWS
            Instance.InputManager = new WindowsDefaultInputManager();
#endif
#if WINDOWS_PHONE || IOS
            Instance.InputManager = new WP7DefaultInputManager();
#endif
#if XBOX
            Instance.InputManager = new Xbox360InputManager();
#endif
            Instance.ServiceProvider = new ServiceManager(graphicsService);
            Instance.ContentManager = new DefaultContentManager();
            Instance.AnimationManager = new DefaultAnimationManager();
            Instance.CameraManager = new DefaultCameraManager();
            Instance.TimeManager = new DefaultTimeManager();
            Instance.SerializationManager = new DefaultSerializationManager();
            Instance.EditorManager = new DefaultEditorManager();
            Instance.IOManager = new DefaultIOManager();
            Instance.LanguageManager = new DefaultLanguageManager();
            Instance.ResourceManager = new DefaultResourceManager();
            Instance.StorageDeviceManager = new DefaultStorageDeviceManager();

            Instance.FinishLoading();

#if TOUCH
            SoundSystem.StartedGame();
#endif
        }
        /// <summary>
        /// Initializes the Engine with all the Default Managers(RenderManagers and DevManagers
        /// are NOT added)
        /// </summary>
        /// <param name="graphicsService"></param>
        public static void InitializeEngineWithDefaultManagers(IGraphicsDeviceService graphicsService)
        {
            EngineFolders folders = new EngineFolders();
            InitializeEngineWithDefaultManagers(folders, graphicsService);
        }

        /// <summary>
        /// Initializes the Engine only with the I/O part, so you can serialize
        /// stuff or work with files. Brain's Language Editor uses this,
        /// so it doesn't need to fully initialize the Engine just to save 
        /// a Language File.
        /// </summary>
        public static void InitializeEngineOnlyIO()
        {
            EngineFolders folder = new EngineFolders();
            Instance = new Engine();
            Instance.EngineFolders = folder;

            Instance.IOManager = new DefaultIOManager();
            Instance.SerializationManager = new DefaultSerializationManager();

            Instance.FinishLoading();
        }

        #region Essential

        /// <summary>
        /// NECESSARY for the operation of the Engine
        /// </summary>
        public IScreenManager ScreenManager;

        /// <summary>
        /// NECESSARY for the operation of the Engine
        /// </summary>
        public IInputManager InputManager;

        /// <summary>
        /// NECESSARY for the operation of the Default Content Manager
        /// </summary>
        public IServiceProvider ServiceProvider;

        /// <summary>
        /// NECESSARY to load content(necessary for every game on earth)
        /// </summary>
        public IContentManager ContentManager;
        #endregion

        #region Managers

        /// <summary>
        /// Manager for Artifial Inteligences
        /// </summary>
        public IAIManager AIManager;

        /// <summary>
        /// Used for any kind of key-framed animation
        /// </summary>
        public IAnimationManager AnimationManager;

        /// <summary>
        /// Manages the Engine's Current Cameras. TO-DO: Support
        /// animation system, making change of Cameras animated and easy
        /// </summary>
        public ICameraManager CameraManager;

        /// <summary>
        /// Shows up a Console on Screen if you press F4 (or if the ShouldDraw/ShouldUpdate property is true)
        /// </summary>
        public IConsoleManager ConsoleManager;

        /// <summary>
        /// Shows information easily on-screen
        /// </summary>
        public IDebugManager DebugManager;

        /// <summary>
        /// Manages the In-Game Editor
        /// </summary>
        public IEditorManager EditorManager;

        public DefaultIOManager IOManager;

        /// <summary>
        /// Manages the Physics in 2D
        /// </summary>
        public IPhysics2DManager Physics2DManager;

        /// <summary>
        /// Manages the Physics in 3D
        /// </summary>
        public IPhysics3DManager Physics3DManager;

        /// <summary>
        /// Necessary for 2d rendering (strings, textures)
        /// </summary>
        public IRenderManager2D RenderManager2D;

        /// <summary>
        /// Necesssary for 3d rendering
        /// </summary>
        public IRenderManager3D RenderManager3D;

        public IResourceManager ResourceManager;

        public ISerializator SerializationManager;

        public IStereoscopicManager StereoscopicManager;

        public IStorageDeviceManager StorageDeviceManager;


        /// <summary>
        /// Records time
        /// </summary>
        public ITimeManager TimeManager;
        
        #endregion

        /// <summary>
        /// If the Engine is currently paused (the user paused the game)
        /// </summary>
        public bool Paused { get; set; }

        public EngineFolders EngineFolders;

        public DefaultLanguageManager LanguageManager;

        /// <summary>
        /// If the Engine is currently running under an Editor
        /// </summary>
        public IEditorForm Editor;

        public bool RunningEditor;
        public bool OnDevelopment;

        public List<IEngineManager> AllManagers { get; protected set; }
        public List<IUpdateable> UpdateableManagers { get; protected set; }
        public List<IDrawable> DrawableManagers { get; protected set; }

        public GraphicsDevice GraphicsDevice;
        private Engine()
        {
            AllManagers = new List<IEngineManager>();
            UpdateableManagers = new List<IUpdateable>();
            DrawableManagers = new List<IDrawable>();
        }

        public void Unload()
        {
            if (ScreenshotTarget != null && !ScreenshotTarget.IsDisposed)
            {
                ScreenshotTarget.Dispose();
                ScreenshotTarget = null;
            }
        }

        public T GetManager<T>()
        {
            for (int i = 0; i < AllManagers.Count; i++)
            {
                IEngineManager manager = AllManagers[i];
                if (manager is T)
                {
                    return (T)manager;
                }
            }
            return default(T);
        }

        internal void addManager(IEngineManager manager)
        {
            if (manager != null)
            {
                this.AllManagers.Add(manager);
            }
        }
        internal void removeManager(IEngineManager manager)
        {
            if (manager != null)
            {
                this.AllManagers.Remove(manager);
            }
        }
       
        public void AddManager(IEngineManager manager)
        {
            if (manager != null)
            {
                addManager(manager);
            }

            if (manager is IAIManager)
            {
                this.AIManager = manager as IAIManager;
                return;
            }
            if (manager is IAnimationManager)
            {
                this.AnimationManager = manager as IAnimationManager;
                return;
            }
            if (manager is ICameraManager)
            {
                this.CameraManager = manager as ICameraManager;
                return;
            }
            if (manager is IContentManager)
            {
                this.ContentManager = manager as IContentManager;
                return;
            }
            if (manager is IDebugManager)
            {
                this.DebugManager = manager as IDebugManager;
                return;
            }
            if (manager is IEditorManager)
            {
                this.EditorManager = manager as IEditorManager;
                return;
            }
            if (manager is IInputManager)
            {
                this.InputManager = manager as IInputManager;
                return;
            }
            if (manager is IPhysics2DManager)
            {
                this.Physics2DManager = manager as IPhysics2DManager;
                return;
            }
            if (manager is IPhysics3DManager)
            {
                this.Physics3DManager = manager as IPhysics3DManager;
                return;
            }
            if (manager is IRenderManager3D)
            {
                this.RenderManager3D = manager as IRenderManager3D;
                return;
            }
            if (manager is IRenderManager2D)
            {
                this.RenderManager2D = manager as IRenderManager2D;
                return;
            }
            if (manager is IResourceManager)
            {
                this.ResourceManager = manager as IResourceManager;
                return;
            }
            if (manager is IScreenManager)
            {
                this.ScreenManager = manager as IScreenManager;
                return;
            }
            if (manager is ISerializator)
            {
                this.SerializationManager = manager as ISerializator;
                return;
            }
            if (manager is IStorageDeviceManager)
            {
                this.StorageDeviceManager = manager as IStorageDeviceManager;
                return;
            }
            if (manager is IStereoscopicManager)
            {
                this.StereoscopicManager = manager as IStereoscopicManager;
                return;
            }
            if (manager is ITimeManager)
            {
                this.TimeManager = manager as ITimeManager;
                return;
            }
            
            if (manager is IUpdateable)
            {
                this.UpdateableManagers.Add(manager as IUpdateable);
            }
            if (manager is IDrawable)
            {
                this.DrawableManagers.Add(manager as IDrawable);
            }
        }
        public void RemoveManager(IEngineManager manager)
        {
            if (manager != null)
            {
                removeManager(manager);
            }

            if (manager is IAIManager)
            {
                this.AIManager = null;
                return;
            }
            if (manager is IAnimationManager)
            {
                this.AnimationManager = null;
                return;
            }
            if (manager is ICameraManager)
            {
                this.CameraManager = null;
                return;
            }
            if (manager is IContentManager)
            {
                this.ContentManager = null;
                return;
            }
            if (manager is IDebugManager)
            {
                this.DebugManager = null;
                return;
            }
            if (manager is IEditorManager)
            {
                this.EditorManager = null;
                return;
            }
            if (manager is IInputManager)
            {
                this.InputManager = null;
                return;
            }
            if (manager is IPhysics2DManager)
            {
                this.Physics2DManager = null;
                return;
            }
            if (manager is IPhysics3DManager)
            {
                this.Physics3DManager = null;
                return;
            }
            if (manager is IRenderManager3D)
            {
                this.RenderManager3D = null;
                return;
            }
            if (manager is IRenderManager2D)
            {
                this.RenderManager2D = null;
                return;
            }
            if (manager is IResourceManager)
            {
                this.ResourceManager = null;
                return;
            }
            if (manager is IScreenManager)
            {
                this.ScreenManager = null;
                return;
            }
            if (manager is ISerializator)
            {
                this.SerializationManager = null;
                return;
            }
            if (manager is IStorageDeviceManager)
            {
                this.StorageDeviceManager = null;
                return;
            }
            if (manager is IStereoscopicManager)
            {
                this.StereoscopicManager = null;
                return;
            }
            if (manager is ITimeManager)
            {
                this.TimeManager = null;
                return;
            }

            if (manager is IUpdateable)
            {
                this.UpdateableManagers.Remove(manager as IUpdateable);
            }
            if (manager is IDrawable)
            {
                this.DrawableManagers.Remove(manager as IDrawable);
            }
        }

        public Game Game { get; protected set; }

        public void AssignGame(Game game)
        {
            this.Game = game;
        }

        public void Exit()
        {
            if (Game != null)
            {
                Game.Exit();
                Game = null;
            }
        }


        public void FinishLoading()
        {
            addManager(this.ScreenManager);
            addManager(this.AnimationManager);
            addManager(this.CameraManager);
            addManager(this.ConsoleManager);
            addManager(this.ContentManager);
            addManager(this.DebugManager);
            addManager(this.EditorManager);
            addManager(this.InputManager);
            addManager(this.Physics2DManager);
            addManager(this.Physics3DManager);
            addManager(this.RenderManager2D);
            addManager(this.RenderManager3D);
            addManager(this.SerializationManager);
            addManager(this.StorageDeviceManager);
            addManager(this.TimeManager);
        }

        /// <summary>
        /// Engine's Game Time
        /// </summary>
        public GameTime GameTime;

        public float UpdateTime { get; protected set; }
        public float DrawTime { get; protected set; }

        public bool DebugMode { get; set; }

        public virtual void Update(GameTime GameTime)
        {
            if (GameTime == null)
            {
                this.GameTime = TimeManager.MakeGameTimeUpdate();
            }
            else
            {
                this.GameTime = GameTime;
            }
            this.TimeManager.StartMeasuring("Update");


            if (InputManager.IsKeyPress(Keys.F3))
            {
                this.DebugMode = !this.DebugMode;
            }

            if (InputManager.ShouldUpdate)
            {                
                InputManager.Update();
            }
            if (ScreenManager.ShouldUpdate)
            {
                ScreenManager.Update();
            }
            if (AnimationManager != null && AnimationManager.ShouldUpdate)
            {
                AnimationManager.Update();
            }
            if (RenderManager3D != null && RenderManager3D.ShouldUpdate)
            {
                RenderManager3D.Update();
            }
            if (Physics2DManager != null && Physics2DManager.ShouldUpdate)
            {
                Physics2DManager.Update();
            }
            if (Physics3DManager != null && Physics3DManager.ShouldUpdate)
            {
                Physics3DManager.Update();
            }
            if (AIManager != null && AIManager.ShouldUpdate)
            {
                AIManager.Update();
            }
            if (StorageDeviceManager != null && StorageDeviceManager is IUpdateable)
            {
                IUpdateable updateable = StorageDeviceManager as IUpdateable;
                if (updateable.ShouldUpdate)
                {
                    updateable.Update();
                }
            }

            for (int i = 0; i < UpdateableManagers.Count; i++)
            {
                IUpdateable update = UpdateableManagers[i];
                if (update.ShouldUpdate)
                {
                    update.Update();
                }
            }

            if (DebugManager != null && DebugManager.ShouldUpdate)
            {
                DebugManager.Update();
            }

            this.UpdateTime = this.TimeManager.EndMeasuring("Update");
        }

        public RenderTarget2D ScreenshotTarget { get; protected set; }

        public void TakeScreenshot()
        {
            PresentationParameters pp = GraphicsDevice.PresentationParameters;
            ScreenshotTarget = new RenderTarget2D(GraphicsDevice, pp.BackBufferWidth,
                pp.BackBufferHeight, false, SurfaceFormat.Color,
                DepthFormat.Depth24Stencil8);
            GraphicsDevice.SetRenderTarget(ScreenshotTarget);
            screenshoting = true;
        }

        private bool screenshoting = false;
        public virtual void Draw(GameTime GameTime)
        {
            if (GameTime == null)
            {
                this.GameTime = TimeManager.MakeGameTimeDraw();
            }
            else
            {
                this.GameTime = GameTime;
            }
            this.TimeManager.StartMeasuring("Draw");


            if (InputManager.IsKeyPress(Keys.F5) && OnDevelopment)
            {
                if (ScreenshotTarget != null)
                {
                    ScreenshotTarget.Dispose();
                    ScreenshotTarget = null;
                }
                TakeScreenshot();
            }

            if (RenderManager2D != null && RenderManager2D.ShouldDraw)
            {
                RenderManager2D.Draw();
            }
            if (RenderManager3D != null && RenderManager3D.ShouldDraw)
            {
                RenderManager3D.Draw();
            }

            if (AIManager != null && AIManager.ShouldDraw)
            {
                AIManager.Draw();
            }

            if (ScreenManager.ShouldDraw)
            {
                ScreenManager.Draw();
            }
            if (StorageDeviceManager != null && StorageDeviceManager is IDrawable)
            {
                IDrawable drawable = StorageDeviceManager as IDrawable;
                if (drawable.ShouldDraw)
                {
                    drawable.Draw();
                }
            }

            for (int i = 0; i < DrawableManagers.Count; i++)
            {
                IDrawable draw = DrawableManagers[i];
                if (draw.ShouldDraw)
                {
                    draw.Draw();
                }
            }

            if (RenderManager3D != null)
            {
                RenderManager3D.EndDraw();
            }
            if (RenderManager2D != null)
            {
                if (Debug)
                {
                    TimeManager.StartMeasuring("RenderManager2D");
                }
                RenderManager2D.EndDraw();
                if (Debug)
                {
                    TimeManager.EndMeasuring("RenderManager2D");
                }
            }


            if (DebugManager != null && DebugManager.ShouldDraw)
            {
                DebugManager.Draw();
            }

            if (screenshoting)
            {
                GraphicsDevice.SetRenderTarget(null);

#if WINDOWS
                DirectoryInfo info = new DirectoryInfo(
                    Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
                    "Brain 2 Game Engine\\Screenshots"));
                if (!info.Exists)
                {
                    info.Create();
                }
                int count = info.GetFiles().Length;
                string fileName = Path.Combine(info.FullName, "screenshot" + count + ".png");
                using (FileStream stream = new FileStream(fileName, FileMode.Create))
                {
                    ScreenshotTarget.SaveAsPng(
                        stream,
                        ScreenshotTarget.Width,
                        ScreenshotTarget.Height);
                }
#endif

                screenshoting = false;
            }

            this.DrawTime = this.TimeManager.EndMeasuring("Draw");
        }

        public virtual void Reset()
        {
            Engine.Instance.Paused = false;

            if (ScreenshotTarget != null && !ScreenshotTarget.IsDisposed)
            {
                ScreenshotTarget.Dispose();
                ScreenshotTarget = null;
            }

            for (int i = 0; i < AllManagers.Count; i++)
            {
                IEngineManager manager = AllManagers[i];
                manager.Reset();
            }

            GC.Collect();
        }
    }
}
